/**
 * File: CurtainPipelineFactory.java
 * Created by: mhaimel
 * Created on: 3 Jul 2009
 * CVS:  $Id: CurtainPipelineManager.java,v 1.3 2009/12/07 09:47:44 mhaimel Exp $
 */
package uk.ac.ebi.curtain.pipeline;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.Future;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.ebi.curtain.CurtainContext;
import uk.ac.ebi.curtain.controller.ResultCollector;
import uk.ac.ebi.curtain.management.ControllerInfo;
import uk.ac.ebi.curtain.management.CurtainControllerFactory;
import uk.ac.ebi.curtain.management.IController;
import uk.ac.ebi.curtain.management.IControllerReport;
import uk.ac.ebi.curtain.management.IInterceptor;
import uk.ac.ebi.curtain.management.InterceptorInfo;
import uk.ac.ebi.curtain.management.impl.Intermediator;
import uk.ac.ebi.curtain.util.ReflectionUtils;
import uk.ac.ebi.curtain.utils.CurtainUncheckedException;
import uk.ac.ebi.velvet.exec.ProcessorService;

import com.google.inject.Inject;

/**
 * @author mhaimel
 *
 */
public class CurtainPipelineManager {
	private Log log = null;

	private ProcessorService processorService;
	
	private final Map<String, ResultCollector> resultMap = new HashMap<String, ResultCollector>();
	private final Map<String, SortedMap<Integer, Set<String>>> type2idxMap = new HashMap<String, SortedMap<Integer,Set<String>>>(); 
	private final Map<String,IController<Serializable, Serializable>> name2Processor = new HashMap<String, IController<Serializable,Serializable>>();
	private final Map<String, Future<IControllerReport>> name2Futurama = new HashMap<String, Future<IControllerReport>>(); 
	
	private CurtainControllerFactory factory;
	private CurtainContext context;

	public void startPipelines() {
		for(Entry<String, IController<Serializable, Serializable>> contr: name2Processor.entrySet()){
			Future<IControllerReport> future = getProcessorService().process(contr.getValue());
			name2Futurama.put(contr.getKey(), future);
		}
	}
	
	public void startPipeline(String type) {
		for (SortedMap<Integer, Set<String>> map : type2idxMap.values()){
			for(Set<String> set : map.values()){
				for(String name : set){
					IController<Serializable, Serializable> controller = name2Processor.get(name);
					Future<IControllerReport> future = getProcessorService().process(controller);
					name2Futurama.put(name, future);
				}
			}
		}
	}

	/**
	 * 
	 */
	public CurtainPipelineManager() {
		
	}		
//	public void connectPipes(){
//		for(ControllerType type : ControllerType.values()){
//			connectPipeline(type);
//		}
//	}
	
	private ResultCollector getResultCollector(String type) {
		ResultCollector result = resultMap.get(type);
		if(null == result){
			result = new ResultCollector();
			resultMap.put(type, result);
		}
		return result;
	}

	public void connectPipeline(String type) {
		
		SortedMap<Integer, Collection<ControllerInfo>> contrMap = getFactory().getControllerMap(type);
		if(null != contrMap && !contrMap.isEmpty()){
			List<Integer> sequList = decreasingList(contrMap.keySet());
			
			ResultCollector result = getResultCollector(type);
			
//			Collection<IController<Serializable, Serializable>> contrColl = new ArrayList<IController<Serializable,Serializable>>();
			Collection<IInterceptor<Serializable>> contrColl = new ArrayList<IInterceptor<Serializable>>();
			Intermediator imed = null;
			for(Integer sequId : sequList){
				Collection<IInterceptor<Serializable>> tmpList = contrColl;
				contrColl = new ArrayList<IInterceptor<Serializable>>();
//				Collection<IController<Serializable, Serializable>> tmpList = contrColl;
//				contrColl = new ArrayList<IController<Serializable,Serializable>>();
				
				imed = new Intermediator();
				if(tmpList.isEmpty()){
					imed.registerTarget(result);
//					cInst.addInterceptor(result);
				} else {
					for(IInterceptor<Serializable> inter : tmpList){
						imed.registerTarget(inter);
//						cInst.addInterceptor(inter);
					}
				}
				
				for(ControllerInfo info : contrMap.get(sequId)){
					IController<Serializable, Serializable> cInst = ReflectionUtils.newInstance(info.getClassName());
					cInst.setParallel(info.getParallel());
					cInst.setQueueName(info.getName());
					
					imed.registerSource(info.getName());
					
					getContext().getInjector().injectMembers(cInst);
					
					cInst.addInterceptor(imed);
					addController(info, cInst);
					contrColl.add(cInst);
//					contrColl.add(cInst);
				}			
			}
			if(null == imed){
				imed.registerSource(getStartInfo().getOriginName());
			}
		} else {
			throw new IllegalArgumentException(type + " not found!!!");
		}
	}
	
	public boolean isFinished(String type){
		return getResultCollector(type).isFinished();
	}
	
	public <T> List<T> getResult(String type){
		return getResultCollector(type).getResultList();
	}
	
	public boolean hasController(String name){
		return name2Processor.containsKey(name);
	}
	
	public void handleController(String name, Serializable input){
		handleController(name, input,getStartInfo());
	}

	
	public void handleController(String name, Serializable input, InterceptorInfo info){
		getLog().debug("Handle input for controller name "+name+": " + input);
		IController<Serializable, Serializable> contr = name2Processor.get(name);
		if(hasController(name) && contr != null){
			contr.handle(input,info);
		} else {
			throw new CurtainUncheckedException("Controller does not exist with name " + name);
		}
	}
	
	public void handle(String type, Serializable input){
		handle(type, input,getStartInfo());
	}
	
	private InterceptorInfo getStartInfo() {
		return new InterceptorInfo("start");
	}

	public void handle(String type, Serializable input, InterceptorInfo info){
		getLog().debug("Handle input for controller type "+type+": " + input);
		for(IController<Serializable, Serializable> contr: getFirst(type)){
			contr.handle(input,info);
		}
	}
	
	
	private CurtainContext getContext() {
		return context;
	}
	
	@Inject
	public void setContext(CurtainContext context) {
		this.context = context;
	}
	
	private Log getLog() {
		if(null == log){
			log = LogFactory.getLog(this.getClass());
		}
		return log;
	}
	
	public void shutdownAndWait(String type, boolean force) throws CurtainUncheckedException{
		shutdown(type,force);
		waitToFinish(type);
	}

	private void waitToFinish(String type) {
		for(Entry<Integer, Set<String>> entry : type2idxMap.get(type).entrySet()){
			for(String name : entry.getValue()){
				Future<IControllerReport> future = name2Futurama.get(name);
				try {
					IControllerReport value = future.get();
					getLog().info("Pipeline " + name + " finished with: " + value.reportSummary());
				} catch (Exception e) {
					throw new CurtainUncheckedException("Problems with pipeline " +name,e);
				} 
			}
		}
	}
	
	public void shutdownAndWait(boolean force) throws CurtainUncheckedException{
		shutdownPipelines(force);
		for(String type : type2idxMap.keySet()){
			waitToFinish(type);
		}
		shutdownProcessorService(force);
	}
	
	public void shutdown(boolean force){
		shutdownPipelines(force);
		shutdownProcessorService(force);
	}

	private void shutdownProcessorService(boolean force) {
		getProcessorService().shutdown(force);
	}

	private void shutdownPipelines(boolean force) {
		for(String type : type2idxMap.keySet()){
			shutdown(type,force);
		}
	}
	
	public void shutdown(String type, boolean force){
		for(IController<Serializable, Serializable> contr: getFirst(type)){
			contr.shutDown(force,getStartInfo());
		}
	}
	
	private Collection<IController<Serializable, Serializable>> getFirst(String type){
		Collection<IController<Serializable, Serializable>> retColl = new ArrayList<IController<Serializable,Serializable>>();
		SortedMap<Integer, Set<String>> sortedMap = type2idxMap.get(type);
		if(null != sortedMap && !sortedMap.isEmpty()){
			Integer firstKey = sortedMap.firstKey();
			for(String name : sortedMap.get(firstKey)){
				retColl.add(name2Processor.get(name));
			}
		}
		return retColl;
	}
		
	private void addController(ControllerInfo info,IController<Serializable, Serializable> inst) {
		name2Processor.put(info.getName(), inst);
		SortedMap<Integer, Set<String>> map = type2idxMap.get(info.getType());
		if(null == map){
			map = new TreeMap<Integer, Set<String>>();
			type2idxMap.put(info.getType(), map);
		}
		Set<String> sequSet = map.get(info.getSequence());
		if(null == sequSet){
			sequSet = new HashSet<String>();
			map.put(info.getSequence(), sequSet);
		}
		sequSet.add(info.getName());
	}

	private List<Integer> decreasingList(Collection<Integer> idColl){
		List<Integer> sequList = new ArrayList<Integer>(idColl);
		Collections.sort(sequList);
		Collections.reverse(sequList);	
		return sequList;
	}

	public CurtainControllerFactory getFactory() {
		return factory;
	}
	
	@Inject
	public void setFactory(CurtainControllerFactory factory) {
		this.factory = factory;
	}
	@Inject
	public void setProcessorService(ProcessorService processorService) {
		this.processorService = processorService;
	}

	public ProcessorService getProcessorService() {
		return processorService;
	}	
}
